﻿using System;
using System.Collections.Generic;
using PhiStore.Policies;
using PhiStore.ReturnCodes;
using PhiStore.Threading;

namespace PhiStore
{
    internal class Prefetcher<T> : Boostable
    {
        HashSet<string> prefetchables;
        HashSet<string> checkedKeys;
        PhiClient<T> internalClient;

        Channel<PrefetchRequest> toPrefetcher = new Channel<PrefetchRequest>(1);
        Channel<object> fromPrefetcher = new Channel<object>(50); //2 should be enough

        private bool AlreadyConnected = false;

        private PrefetchRequest request;
        private PrefetchRequest previousRequest;

        public Prefetcher(bool encryptConnection, string username, string password)
        {
            prefetchables = new HashSet<string>();
            checkedKeys = new HashSet<string>();
            internalClient = new PhiClient<T>(encryptConnection, false, username, password);
        }

        public void Initialize(bool compress, string encryptionKey)
        {
            try
            {
                lock (internalClient)
                {
                    internalClient.Initialize(compress, encryptionKey);
                }
            }
            catch
            { }
        }

        public void Connect(System.Net.IPAddress ip, int port)
        {
            if (!AlreadyConnected)
            {
                try
                {
                    lock (internalClient)
                    {
                        internalClient.Connect(ip, port);
                    }
                    Booster.Boost(this);
                    AlreadyConnected = true;
                }
                catch
                { }
            }
        }

        private void SetPrefetchable(string key, bool prefetchable)
        {
            if(!checkedKeys.Contains(key)){
                if (prefetchable && !prefetchables.Contains(key))
                {
                    prefetchables.Add(key);
                }

                if (!prefetchable && prefetchables.Contains(key))
                {
                    prefetchables.Remove(key);
                }
                checkedKeys.Add(key);
            }
        }

        public void AddPrefetchable(string key, Policy p)
        {
            SetPrefetchable(key, p.Prefetchable);
        }

        public void RemovePrefetchable(string key)
        {
            SetPrefetchable(key, false);
        }

        public bool Prefetching(string key, long index, out T result)
        {
            result = default(T);

            bool success = false;

            object prefetchResult = null;
            if (request != null && request.Key == key && request.Index == index) //HIT
            {
                prefetchResult = fromPrefetcher.Receive();

                if (prefetchResult != null) //hit
                {
                    result = (T)prefetchResult;
                    success = true;
                }
                previousRequest = request;
                request = null;

                //Log.Instance.AddLogEntry("PREFETCH", "HIT");
            }
            else
            { //MISS

                //Log.Instance.AddLogEntry("PREFETCH", "Miss");
            }

            return success;
        }
        public void Stop()
        {
            toPrefetcher.Poison();
            fromPrefetcher.Poison();
            lock (internalClient)
            {
                internalClient.Disconnect();
            }
        }

        public void Prefetch(string key, long index)
        {
            if(!checkedKeys.Contains(key)){ // not checked e.g. created in another client
                Log.Instance.AddLogEntry("PREFETCHER", key + " isn't checked");
                lock (internalClient)
                {
                    try
                    {
                        Policy p = internalClient.GetPolicy(key);
                        AddPrefetchable(key, p);
                    }
                    catch
                    {
                        SetPrefetchable(key, false);
                    }
                }
                checkedKeys.Add(key);
                Log.Instance.AddLogEntry("PREFETCHER", key + ( prefetchables.Contains(key) ? " is prefetchable" : "isn't prefetchable"));
            }

            if (prefetchables.Contains(key)) //it's prefetchable 
            {
                if (request != null)
                {
                    fromPrefetcher.Receive();
                    previousRequest = request;
                }

                request = new PrefetchRequest(key, index);
                if (previousRequest != null && request.Key == previousRequest.Key && request.Index == previousRequest.Index + 1)
                {
                    //this looks like a sequential scan
                    toPrefetcher.Send(request.Copy());
                }
                else
                {
                    previousRequest = request;
                    request = null;
                }
            }
        }

        public void Run()
        {
            try
            {
                while (true)
                {
                    string key;
                    long index;

                    PrefetchRequest pr = toPrefetcher.Receive();

                    key = pr.Key;
                    index = pr.Index;

                    object result = null;
                    try
                    {
                        lock (internalClient)
                        {
                            result = internalClient.ListRead(key, index);
                        }
                    }
                    catch (PhiException e)
                    {
                        Log.Instance.AddLogEntry("PREFETCH", "Failure: " + e.ToString());
                    }

                    fromPrefetcher.Send(result);
                }
            }
            catch (Exception e)
            {
                Log.Instance.AddLogEntry("PREFETCH", "Complete Failure: " + e.ToString());
            }
        }
    }

    internal class PrefetchRequest
    {
        public string Key;
        public long Index;
        public PrefetchRequest(string key, long index)
        {
            this.Key = key;
            this.Index = index;
        }
        public PrefetchRequest Copy()
        {
            return new PrefetchRequest(Key, Index);
        }
    }
}
